/**
 * 为某些指定的HTML元素添加弹出框
 *
 * - 需要 jQuery 及 CSS 3.0+ 支持
 * - 需要 event-patch.js
 *
 * 一般启用调用语法：
 *      $(selector).popupSelector (options)
 *
 * 有效的 option:
 *  opt name        type                        optional    memo
 *  body            string|selector             Y           将显示在弹出框中
 *                  jQuery object|function
 *                                                          若类型为string，则直接显示该文本内容
 *                                                          若为selector，将调用 $(selector)后作为内容显示
 *                                                          若为jQuery对象，则直接作为内容显示
 *                                                          若为function, 取返回值进行以上判断
 *  auto_clean      boolean                     Y           是否在隐藏弹出框后自动清除内容
 *                                                          默认 false
 *  auto_close      boolean                     Y           是否自动隐藏弹出框
 *                                                          默认 false
 *  timeout         number                      Y           若auto_close为true，在 timeout 毫秒后自动隐藏弹出框
 *  hide_when_mask_clicked  boolean             Y           当点击遮罩时是否隐藏弹出框
 *                                                          默认 true
 *  show            function                    Y           弹出框显示之前触发，若返回false，则阻止弹出框继续弹出
 *  shown           function                    Y           弹出框显示之后触发
 *  hide            function                    Y           弹出框隐藏之前触发, 若返回false，则阻止弹出框继续隐藏
 *  hidden          function                    Y           弹出框隐藏之后触发
 *
 * 方法 :
 *  $(selector).popupSelector ('show')          显示一个弹出框
 *  $(selector).popupSelector ('hide')          隐藏一个弹出框
 *  $(selector).popupSelector ('dispose')       销毁一个弹出框
 *
 * @author: seth.yang [seth_yang@126.com]
 */
(function ($) {
    var panel, mask, content, elements = {}, handler = {}, W = $(window).width (), H = $(window).height ();
    if ($.fn.popupSelector) {
        return;
    }

    /**
     * 当指定的HTML元素被点击时触发的事件
     *
     * 显示弹出框并适当调整弹出框的尺寸
     */
    function on_element_clicked () {
        var pos, self = $(this).addClass('ps-active-control'),
            options = self.data ('settings'), w, h, body, shown = self.attr ('data-shown');
        if ('true' === shown) {
            console.debug ('the panel already shown, ignore this action');
            return;
        }
        if (false === Patch.trigger_event (this, 'show.bs.popup')) {
            return;
        }
        mask.show ();
        pos = _location.apply (this);
        handler.current = self.attr ('data-ps-id');
        if ('body' in options) {
            body = options ['body'];

            if (typeof body === 'function') {
                body = body.apply (this);
            }

            if (body !== undefined && body != null) {
                if (typeof body === 'string' && !(body.startsWith('.') || body.startsWith('#'))) {
                    $('<div/>').text(body).appendTo(content);
                } else {
                    body = $(body);
                    self.data('parent', body.parent()).data('body', body);
                    body.show().appendTo(content);
                }
            }
        }
        content.css ({width:'',height:''});
        var css = {};
        panel.css (pos).show ();
        self.attr ('data-shown', true);
        w = content.width ();
        h = content.height ();
        if (pos.left + w + 40 > W) {
            css.width = W - 50 - pos.left;
        }
        if (pos.top + h + 40 > H) {
            css.height = H - 50 - pos.top;
        }
        content.css (css);
        panel.animate ({opacity:1}, 'slow', function () {
            Patch.trigger_event (self, 'shown.bs.popup');

            if (true === options ['auto_close']) {
                var timeout = options ['timeout'];
                if (isNaN (timeout) || timeout <= 0) {
                    timeout = 1500;     // 默认 1.5s 后自动隐藏
                }
                handler.timer = setTimeout (close_panel, timeout);
            }
        });
    }

    function close_panel () {
        var src, opts;
        if (('current' in handler) && (handler.current in elements)) {
            src = elements [handler.current];
            if (false === Patch.trigger_event(src, 'hide.bs.popup')) {
                return;
            }
        }
        panel.animate({opacity: 0}, 'slow', function () {
            panel.hide();
            if (src) {
                src.removeClass ('ps-active-control');
                src.removeAttr ('data-shown');
                Patch.trigger_event (src, 'hidden.bs.popup');

                delete handler.current;

                opts = src.data ('settings');

                if (('auto_clean' in opts) && (true === opts ['auto_clean'])) {
                    var parent = src.data ('parent'), body = src.data ('body');
                    if (parent && body) {
                        body.appendTo (parent);
                        src.removeData ('parent').removeData ('body');
                    } else {
                        content.empty ();
                    }
                }
            }
            mask.hide ();
        });
        if (handler.timer) {
            clearTimeout (handler.timer);
            delete handler.timer;
        }
    }

    function on_mask_clicked () {
        if (('current' in handler) && (handler.current in elements)) {
            var src = elements [handler.current], opts = src.data ('settings');
            if (true === opts['hide_when_mask_clicked']) {
                close_panel ();
            }
        }
    }

    function _location () {
        var self = $(this), pos = self.offset (), _top = pos.top + self.height();
        _top += Patch.get_css_number_property (self, 'padding-bottom');
        _top += Patch.get_css_number_property (self, 'padding-top');
        _top += Patch.get_css_number_property (self, 'border-top-width');
        _top += Patch.get_css_number_property (self, 'border-bottom-width');
        _top += Patch.get_css_number_property (panel, 'margin-top');
        return {top:_top, left:pos.left};
    }

    function dispose (src) {
        var self = $(src), id = self.attr ('data-ps-id');
        if (id === handler.current) {
            close_panel ();
        }
        self.removeData ('settings');
        self.removeAttr ('data-ps-id');
        self.removeAttr ('data-shown');
        self.unbind ('click', on_element_clicked);
        if (self.is ('input')) {
            self.removeProp ('readonly');
        }
        if (id in elements) {
            delete elements [id];
        }
    }

    function build_panel () {
        if (!panel) {
            mask = $('<div class="ps-mask"/>').on('click', on_mask_clicked).appendTo($(document.body));
            panel = $('<div class="ps-panel"/>').appendTo($(document.body));
            $('<div class="ps-handler"/>').append($('<div class="ps-handler-line"/>'))
                .append($('<div class="ps-handler-content"/>')).appendTo(panel);
            var _body = $('<div class="ps-body"/>').appendTo(panel);
            content = $('<div class="ps-content"/>').appendTo(_body);
        }
    }

    $.fn.popupSelector = function (opts) {
        console.debug (this);
        if (typeof opts === 'string') {
            switch (opts) {
                case 'dispose': {
                    $(this).each (function () {
                        dispose ($(this));
                    });
                    break;
                }
                case 'show' :
                    $(this).trigger ('click');
                    break;
                case 'hide' :
                    close_panel ();
                    break;
                default:
                    console.warn ('unknown method: ' + opts);
                    break;
            }
        } else if (!panel) {
            build_panel ();
            return $(this).each(function () {
                var self = $(this), _id = 'ps-' + Math.random().toString().substring(2),
                    settings = $.extend({
                        auto_close:false,auto_clean:false,timeout:1500,
                        hide_when_mask_clicked:true
                    }, opts || {});
                self.attr('data-ps-id', _id)
                    .data('settings', settings)
                    .on('click', on_element_clicked);
                elements [_id] = self;
                if (self.is('input')) {
                    self.prop('readonly', true);
                }
            });
        } else {
            return $(this);
        }
    }
})(jQuery);